package org.domon.manager.base.mybatis.page;

import java.sql.Connection;
import java.util.Properties;

import org.apache.ibatis.executor.statement.PreparedStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class DiclectStatementHandlerInterceptor implements Interceptor {

	@Autowired
	private SqlSessionFactoryBean sqlSessionFactory;

	public Object intercept(Invocation invocation) throws Throwable {
		RoutingStatementHandler statement = (RoutingStatementHandler) invocation
				.getTarget();
		PreparedStatementHandler handler = (PreparedStatementHandler) ReflectUtil
				.getClassField(statement, "delegate");
		RowBounds rowBounds = (RowBounds) ReflectUtil.getSuperClassField(
				handler, "rowBounds");
		if (rowBounds.getLimit() > 0
				&& rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT) {
			BoundSql boundSql = statement.getBoundSql();
			String sql = boundSql.getSql();
			String database = ((Configuration) ReflectUtil.getSuperClassField(
					handler, "configuration")).getDatabaseId().toLowerCase();
			switch (database) {
			case "mysql":
				sql = getMysqlLimitString(sql, rowBounds.getOffset(),
						rowBounds.getLimit());
				break;

			case "oracle":
				sql = getOracleLimitString(sql, rowBounds.getOffset(),
						rowBounds.getLimit());
				break;
			}
			ReflectUtil.setClassField(boundSql, "sql", sql);
		}
		return invocation.proceed();
	}

	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	public void setProperties(Properties properties) {
	}

	/**
	 * @param sql
	 * @param offset
	 *            mysql 起始条数
	 * @param limit
	 *            mysql 查询的条数
	 * @return
	 */
	private String getMysqlLimitString(String sql, int offset, int limit) {
		sql = sql.trim();

		// offset:第几页
		// limit:每页几条
		int start = (offset - 1) * limit;

		StringBuffer pagingSelect = new StringBuffer(sql.length() + 30);
		pagingSelect.append("select _table.* from ( ");
		pagingSelect.append(sql);
		pagingSelect.append(" ) _table limit ").append(start).append(",")
				.append(limit);
		return pagingSelect.toString();
	}

	/**
	 * @param sql
	 * @param offset
	 *            Oracle min Rownum
	 * @param limit
	 *            Oracle max Rownum
	 * @return
	 */
	private String getOracleLimitString(String sql, int offset, int limit) {
		sql = sql.trim();

		// offset:第几页
		// limit:每页几条
		int start = (offset - 1) * limit;
		int end = offset * limit;

		boolean isForUpdate = false;
		if (sql.toLowerCase().endsWith(" for update")) {
			sql = sql.substring(0, sql.length() - 11);
			isForUpdate = true;
		}
		StringBuffer pagingSelect = new StringBuffer(sql.length() + 100);
		if (offset > 0) {
			pagingSelect
					.append("select * from ( select row_.*, rownum rownum_ from ( ");
		} else {
			pagingSelect.append("select * from ( ");
		}
		pagingSelect.append(sql);
		if (offset > 0) {
			pagingSelect.append(" ) row_ ) where rownum_ <= " + end
					+ " and rownum_ > " + start);
		} else {
			pagingSelect.append(" ) where rownum <= " + limit);
		}
		if (isForUpdate) {
			pagingSelect.append(" for update");
		}
		return pagingSelect.toString();
	}
}